@actualwave/react-native-codeditor 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/README.md +67 -7
- package/SKILL.md +504 -0
- package/THIRD_PARTY_NOTICES +120 -0
- package/app.plugin.js +1 -1
- package/lib/module/CodeEditor.js +69 -2
- package/lib/typescript/src/CodeEditor.d.ts +20 -2
- package/lib/typescript/src/EditorAPI.d.ts +42 -1
- package/lib/typescript/src/WebViewAPI.d.ts +4 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/package.json +12 -4
- package/src/assets/codemirror/@actualwave_codemirror-lang-embed-core.js +39 -0
- package/src/assets/codemirror/@actualwave_codemirror-lang-embed-css.js +19 -0
- package/src/assets/codemirror/@actualwave_codemirror-lang-embed-glsl.js +1 -0
- package/src/assets/codemirror/@actualwave_codemirror-lang-embed-graphql.js +50 -0
- package/src/assets/codemirror/@actualwave_codemirror-lang-embed-icu-messageformat.js +1 -0
- package/src/assets/codemirror/@actualwave_codemirror-lang-embed-react-native.js +6 -0
- package/src/assets/codemirror/@actualwave_codemirror-lang-embed-sksl.js +7 -0
- package/src/assets/codemirror/@actualwave_codemirror-lang-embed-sql.js +5 -0
- package/src/assets/codemirror/@actualwave_codemirror-lang-embed-tailwind.js +63 -0
- package/src/assets/codemirror/@actualwave_codemirror-lang-glsl.js +20 -0
- package/src/assets/codemirror/@actualwave_codemirror-lang-icu-messageformat.js +11 -0
- package/src/assets/codemirror/@actualwave_codemirror-lang-js-completion.js +72 -0
- package/src/assets/codemirror/@actualwave_codemirror-lang-react-native.js +71 -0
- package/src/assets/codemirror/@actualwave_codemirror-lang-sksl.js +7 -2
- package/src/assets/codemirror/@codemirror_autocomplete.js +9 -9
- package/src/assets/codemirror/@codemirror_commands.js +86 -86
- package/src/assets/codemirror/@codemirror_lang-css.js +2 -2
- package/src/assets/codemirror/@codemirror_lang-go.js +1 -1
- package/src/assets/codemirror/@codemirror_lang-html.js +1 -1
- package/src/assets/codemirror/@codemirror_lang-javascript.js +4 -4
- package/src/assets/codemirror/@codemirror_lang-jinja.js +1 -1
- package/src/assets/codemirror/@codemirror_lang-json.js +1 -1
- package/src/assets/codemirror/@codemirror_lang-less.js +1 -1
- package/src/assets/codemirror/@codemirror_lang-liquid.js +1 -1
- package/src/assets/codemirror/@codemirror_lang-markdown.js +2 -2
- package/src/assets/codemirror/@codemirror_lang-python.js +1 -1
- package/src/assets/codemirror/@codemirror_lang-sql.js +4 -4
- package/src/assets/codemirror/@codemirror_lang-xml.js +1 -1
- package/src/assets/codemirror/@codemirror_language.js +11 -11
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_clike.js +1 -1
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_mathematica.js +1 -1
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_oz.js +1 -1
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_verilog.js +1 -1
- package/src/assets/codemirror/@codemirror_lint.js +5 -5
- package/src/assets/codemirror/@codemirror_merge.js +4 -4
- package/src/assets/codemirror/@codemirror_search.js +8 -8
- package/src/assets/codemirror/@codemirror_state.js +1 -1
- package/src/assets/codemirror/@codemirror_theme-one-dark.js +3 -3
- package/src/assets/codemirror/@codemirror_view.js +6 -6
- package/src/assets/codemirror/@lezer_common.js +2 -2
- package/src/assets/codemirror/@lezer_css.js +1 -1
- package/src/assets/codemirror/@lezer_javascript.js +1 -1
- package/src/assets/codemirror/@lezer_lr.js +1 -1
- package/src/assets/codemirror/@lezer_markdown.js +4 -4
- package/src/assets/codemirror/@lezer_sass.js +1 -1
- package/src/assets/codemirror/_core.js +129 -129
- package/src/assets/codemirror-editor.umd.js +130 -17
- package/src/assets/editor.html +37 -2
- package/lib/typescript/app.plugin.d.ts +0 -3
- package/lib/typescript/babel.config.d.ts +0 -10
- package/lib/typescript/react-native.config.d.ts +0 -2
- package/lib/typescript/scripts/copy-assets.d.ts +0 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.2.1
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- Updated bundled `@actualwave/codemirror-package` assets to 0.1.5, which adds the
|
|
7
|
+
`@actualwave/codemirror-lang-js-completion` language package for JavaScript completion.
|
|
8
|
+
- `js-completion` is added to the example app's `supportExtensions`.
|
|
9
|
+
- Static completions set via `setCompletions()` are now registered as language data instead of
|
|
10
|
+
`autocompletion({ override })`, so they merge with the language's own completion sources
|
|
11
|
+
(including embedded-language ones such as `sql`/`sksl` tagged templates, Tailwind, React Native)
|
|
12
|
+
and are suppressed inside embedded languages.
|
|
13
|
+
|
|
14
|
+
## 1.2.0
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- `languages` prop on `<CodeEditor>` and `api.editor.registerLanguage(name, packageName)` for
|
|
18
|
+
language packages that don't follow the `@codemirror/lang-{name}` naming convention
|
|
19
|
+
(e.g. `{ sksl: '@actualwave/codemirror-lang-sksl' }`). The prop registers before the initial
|
|
20
|
+
`language` is resolved.
|
|
21
|
+
- `languageConfig` prop and `setLanguage(name, config?, embeds?)` — options passed to the
|
|
22
|
+
language factory (e.g. `{ jsx: true, typescript: true }`).
|
|
23
|
+
- `embeds` prop, `api.editor.registerTaggedTemplate()` and `api.editor.registerSupportExtension()`
|
|
24
|
+
for tagged-template DSL embeddings and support extensions in `javascript`.
|
|
25
|
+
- `CompletionItem` and `EmbedsConfig` types are now exported.
|
|
26
|
+
- `THIRD_PARTY_NOTICES`, `SKILL.md` and `CHANGELOG.md` are included in the package.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
- Updated bundled `@actualwave/codemirror-package` assets to 0.1.4.
|
|
30
|
+
- `sksl` (and other `@actualwave/codemirror-lang-*` packages) are no longer pre-registered by the
|
|
31
|
+
bundled codemirror-package (it was an undocumented built-in in 1.1.0). Pass
|
|
32
|
+
`languages={{ sksl: '@actualwave/codemirror-lang-sksl' }}` or call `api.editor.registerLanguage()`
|
|
33
|
+
before using them; otherwise `setLanguage` rejects with "No language support found".
|
|
34
|
+
- `lib/typescript` no longer contains declaration files for repo config files.
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
- Documentation and example for the iOS `editorUri` used `FileSystem.bundleDirectory`, which is not
|
|
38
|
+
available from the default `expo-file-system` entry point in Expo SDK 55. Use
|
|
39
|
+
`new File(Paths.bundle, 'assets/codeditor/editor.html').uri`.
|
package/README.md
CHANGED
|
@@ -144,16 +144,16 @@ npm install expo-file-system
|
|
|
144
144
|
|
|
145
145
|
```tsx
|
|
146
146
|
import { Platform } from 'react-native';
|
|
147
|
-
import
|
|
147
|
+
import { File, Paths } from 'expo-file-system';
|
|
148
148
|
|
|
149
149
|
const IOS_EDITOR_URI = Platform.OS === 'ios'
|
|
150
|
-
? (
|
|
150
|
+
? new File(Paths.bundle, 'assets/codeditor/editor.html').uri
|
|
151
151
|
: undefined;
|
|
152
152
|
|
|
153
153
|
<CodeEditor editorUri={IOS_EDITOR_URI} ... />
|
|
154
154
|
```
|
|
155
155
|
|
|
156
|
-
`
|
|
156
|
+
`Paths.bundle` is the `.app` folder in the bundle (e.g.
|
|
157
157
|
`file:///var/containers/Bundle/Application/<UUID>/MyApp.app/`), which is stable on both
|
|
158
158
|
simulators and real devices regardless of where iOS installed the app.
|
|
159
159
|
|
|
@@ -224,6 +224,9 @@ export default function EditorScreen() {
|
|
|
224
224
|
|---|---|---|---|
|
|
225
225
|
| `content` | `string` | `''` | Initial document content (see [Content model](#content-model) below). |
|
|
226
226
|
| `language` | `string` | `undefined` | Syntax language, e.g. `'javascript'`, `'python'`, `'sql'`. The language module is loaded from the bundled assets on first use. |
|
|
227
|
+
| `languageConfig` | `Record<string, unknown>` | `undefined` | Options passed to the language factory, e.g. `{ jsx: true, typescript: true }` for `'javascript'`. Must be plain serializable data. |
|
|
228
|
+
| `embeds` | `EmbedsConfig` | `undefined` | DSLs embedded in `'javascript'` — highlighting and suggestions for tagged templates (`sql`, `gql`, `css`…) and support extensions (Tailwind, React Native). See [Embedded DSLs](#embedded-dsls-in-javascript). Changing it re-resolves the current language. |
|
|
229
|
+
| `languages` | `Record<string, string>` | `undefined` | Custom language packages as `{ name: packageName }`, for languages that don't follow the `@codemirror/lang-{name}` convention, e.g. `{ sksl: '@actualwave/codemirror-lang-sksl' }`. Registered before the initial `language` is resolved. The package must be bundled in the WebView assets. |
|
|
227
230
|
| `extensions` | `ExtensionSpec[]` | `[]` | Additional CodeMirror 6 extension specs. See [Extension specs](#extension-specs). |
|
|
228
231
|
| `theme` | `string` | `undefined` | Theme name. See [Themes](#themes). |
|
|
229
232
|
| `viewport` | `ViewportSettings` | `undefined` | Controls the `<meta name="viewport">` tag inside the WebView. |
|
|
@@ -310,9 +313,23 @@ api.editor.resetValue(value?: string): Promise<void>
|
|
|
310
313
|
### Language, extensions, theme
|
|
311
314
|
|
|
312
315
|
```ts
|
|
313
|
-
api.editor.setLanguage(name: string): Promise<void>
|
|
316
|
+
api.editor.setLanguage(name: string, config?: object, embeds?: EmbedsConfig): Promise<void>
|
|
314
317
|
// Switches syntax language. Loads the language module on demand.
|
|
318
|
+
// `config` goes to the language factory; `embeds` applies to 'javascript' only.
|
|
315
319
|
// e.g. await api.editor.setLanguage('python')
|
|
320
|
+
// await api.editor.setLanguage('javascript', { jsx: true }, { taggedTemplates: [...] })
|
|
321
|
+
|
|
322
|
+
api.editor.registerTaggedTemplate(packageName: string): Promise<void>
|
|
323
|
+
api.editor.registerSupportExtension(packageName: string, config?: object): Promise<void>
|
|
324
|
+
// Register an embed globally for the page. Call before the language is set to
|
|
325
|
+
// 'javascript', or follow with setLanguage('javascript', ...) to re-resolve.
|
|
326
|
+
// Alternative to the `embeds` prop — see "Embedded DSLs".
|
|
327
|
+
|
|
328
|
+
api.editor.registerLanguage(name: string, packageName: string): Promise<void>
|
|
329
|
+
// Maps a language name to a package that doesn't follow @codemirror/lang-{name}.
|
|
330
|
+
// Takes effect on the next setLanguage(). Alternative to the `languages` prop.
|
|
331
|
+
// e.g. await api.editor.registerLanguage('sksl', '@actualwave/codemirror-lang-sksl');
|
|
332
|
+
// await api.editor.setLanguage('sksl')
|
|
316
333
|
|
|
317
334
|
api.editor.setExtensions(specs: ExtensionSpec[]): Promise<void>
|
|
318
335
|
// Replaces the active extension set.
|
|
@@ -451,9 +468,9 @@ api.editor.unregisterShortcut(key: string): Promise<void>
|
|
|
451
468
|
|
|
452
469
|
```tsx
|
|
453
470
|
<CodeEditor
|
|
454
|
-
onInitialized={(api) => {
|
|
455
|
-
|
|
456
|
-
|
|
471
|
+
onInitialized={async (api) => {
|
|
472
|
+
await api.editor.registerShortcut('Mod-s', 'save');
|
|
473
|
+
await api.editor.registerShortcut('Mod-Enter', 'run');
|
|
457
474
|
}}
|
|
458
475
|
onShortcut={(name) => {
|
|
459
476
|
if (name === 'save') handleSave();
|
|
@@ -559,6 +576,46 @@ await api.editor.setTheme(undefined); // remove theme, use CM6 default
|
|
|
559
576
|
|
|
560
577
|
---
|
|
561
578
|
|
|
579
|
+
## Embedded DSLs in `javascript`
|
|
580
|
+
|
|
581
|
+
The `embeds` prop (or `setLanguage(name, config, embeds)`) mixes extra syntax into the
|
|
582
|
+
`'javascript'` language: highlighting **and** autocomplete suggestions. Packages are bundled
|
|
583
|
+
in the WebView assets; embeds only take effect for `language="javascript"`.
|
|
584
|
+
|
|
585
|
+
```tsx
|
|
586
|
+
<CodeEditor
|
|
587
|
+
language="javascript"
|
|
588
|
+
languageConfig={{ jsx: true, typescript: true }}
|
|
589
|
+
embeds={{
|
|
590
|
+
taggedTemplates: [
|
|
591
|
+
'@actualwave/codemirror-lang-embed-css',
|
|
592
|
+
'@actualwave/codemirror-lang-embed-sql',
|
|
593
|
+
'@actualwave/codemirror-lang-embed-graphql',
|
|
594
|
+
],
|
|
595
|
+
supportExtensions: [
|
|
596
|
+
'@actualwave/codemirror-lang-embed-react-native',
|
|
597
|
+
'@actualwave/codemirror-lang-js-completion',
|
|
598
|
+
['@actualwave/codemirror-lang-embed-tailwind', {}], // [name, config]
|
|
599
|
+
],
|
|
600
|
+
}}
|
|
601
|
+
{...otherProps}
|
|
602
|
+
/>
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
A package must be in the list matching what it exports, otherwise it silently fails to load:
|
|
606
|
+
|
|
607
|
+
| List | Packages | Provides |
|
|
608
|
+
|---|---|---|
|
|
609
|
+
| `taggedTemplates` | `embed-css`, `embed-sql`, `embed-glsl`, `embed-sksl`, `embed-icu-messageformat`, `embed-graphql` | Nested language highlighting + its suggestions (`embed-graphql`: highlighting only) |
|
|
610
|
+
| `supportExtensions` | `embed-tailwind`, `embed-react-native`, `js-completion` | Suggestions (and Tailwind class coloring) on the JS language; no nested language |
|
|
611
|
+
|
|
612
|
+
Tags are matched by name: `css`/`styled`/`styled.X`/`styled(X)` → CSS, `sql`, `gql`, `glsl`,
|
|
613
|
+
`sksl`, `t` → ICU MessageFormat, `tw` → Tailwind. `embed-react-native` needs no tag (JSX, imports,
|
|
614
|
+
`StyleSheet.create`). Only `supportExtensions` accept a config. Pass a stable (memoized/module-level)
|
|
615
|
+
`embeds` object — a new reference re-resolves the language.
|
|
616
|
+
|
|
617
|
+
---
|
|
618
|
+
|
|
562
619
|
## Supported languages
|
|
563
620
|
|
|
564
621
|
Pass any of these to the `language` prop or `api.editor.setLanguage()`:
|
|
@@ -567,6 +624,9 @@ Pass any of these to the `language` prop or `api.editor.setLanguage()`:
|
|
|
567
624
|
`less` `lezer` `liquid` `markdown` `php` `python` `rust` `sass` `sql`
|
|
568
625
|
`vue` `wast` `xml` `yaml`
|
|
569
626
|
|
|
627
|
+
Other bundled packages such as `sksl` are not auto-detected — register them with the
|
|
628
|
+
`languages` prop or `api.editor.registerLanguage()`.
|
|
629
|
+
|
|
570
630
|
Legacy modes (100+ additional languages via `@codemirror/legacy-modes`) can be loaded
|
|
571
631
|
manually with `api.editor.loadExtension()`:
|
|
572
632
|
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: react-native-codeditor
|
|
3
|
+
description: >
|
|
4
|
+
Embed and control a CodeMirror 6 code editor inside a React Native WebView using
|
|
5
|
+
@actualwave/react-native-codeditor. Use when adding a code editor to a React Native
|
|
6
|
+
app, controlling editor content/language/theme/cursor/selection/history programmatically,
|
|
7
|
+
registering custom keyboard shortcuts, handling editor events (content changes, history
|
|
8
|
+
size, selection), or setting up native asset copying for Android/iOS.
|
|
9
|
+
license: MIT
|
|
10
|
+
compatibility: React Native ≥ 0.71, react-native-webview ≥ 11, Android WebView 61+, iOS with WKWebView. expo-file-system required for iOS URI resolution.
|
|
11
|
+
metadata:
|
|
12
|
+
package: "@actualwave/react-native-codeditor"
|
|
13
|
+
npm: "https://www.npmjs.com/package/@actualwave/react-native-codeditor"
|
|
14
|
+
repository: "https://github.com/burdiuz/react-native-codeditor"
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# @actualwave/react-native-codeditor — Agent Skill
|
|
18
|
+
|
|
19
|
+
CodeMirror 6 code editor for React Native, embedded in a WebView with a full bidirectional RPC bridge. Fully offline — all CodeMirror assets are bundled. Works on Android and iOS.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Exports
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import CodeEditor from '@actualwave/react-native-codeditor';
|
|
27
|
+
// or named (WebViewAPI is the class itself, exported as a value):
|
|
28
|
+
import { CodeEditor, WebViewAPI } from '@actualwave/react-native-codeditor';
|
|
29
|
+
|
|
30
|
+
// Types
|
|
31
|
+
import type {
|
|
32
|
+
CodeEditorProps, // all props for <CodeEditor />
|
|
33
|
+
WebViewAPIHandlers, // handler callbacks
|
|
34
|
+
WebViewRef, // { injectJavaScript, requestFocus? }
|
|
35
|
+
InitialConfig, // { content?, language?, languageConfig?, embeds?, languages?, extensions?, theme?, viewport? }
|
|
36
|
+
EditorAPI, // full interface of api.editor proxy methods
|
|
37
|
+
ExtensionSpec, // string | [string, string] | [string, object] | unknown
|
|
38
|
+
ViewportSettings, // { intialScale?, maximumScale?, minimumScale?, userScalable?, viewportWidth? }
|
|
39
|
+
CursorPosition, // { line: number, ch: number, index: number }
|
|
40
|
+
HistorySize, // { undo: number, redo: number }
|
|
41
|
+
CompletionItem, // { label, type?, detail?, info? }
|
|
42
|
+
EmbedsConfig, // { taggedTemplates?, supportExtensions? }
|
|
43
|
+
} from '@actualwave/react-native-codeditor';
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install @actualwave/react-native-codeditor react-native-webview
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Copy assets to Android** (required — WebView loads these files at runtime):
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
mkdir -p android/app/src/main/assets/codeditor
|
|
58
|
+
cp -r node_modules/@actualwave/react-native-codeditor/src/assets/* android/app/src/main/assets/codeditor/
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Expo (recommended)** — add plugin to `app.json`, then `expo prebuild`:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{ "expo": { "plugins": ["@actualwave/react-native-codeditor"] } }
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**iOS** — requires `expo-file-system` for the runtime bundle path:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npm install expo-file-system
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import { File, Paths } from 'expo-file-system';
|
|
75
|
+
const IOS_EDITOR_URI = Platform.OS === 'ios'
|
|
76
|
+
? new File(Paths.bundle, 'assets/codeditor/editor.html').uri
|
|
77
|
+
: undefined;
|
|
78
|
+
<CodeEditor editorUri={IOS_EDITOR_URI} ... />
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Android manifest** — add `adjustResize` so the editor shrinks when the keyboard appears:
|
|
82
|
+
|
|
83
|
+
```xml
|
|
84
|
+
<activity android:windowSoftInputMode="adjustResize" ... />
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**After upgrading** — always re-copy assets:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
cp -r node_modules/@actualwave/react-native-codeditor/src/assets/* android/app/src/main/assets/codeditor/
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Basic usage
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
import { useCallback, useRef } from 'react';
|
|
99
|
+
import { KeyboardAvoidingView, Platform } from 'react-native';
|
|
100
|
+
import CodeEditor from '@actualwave/react-native-codeditor';
|
|
101
|
+
import type { WebViewAPI, HistorySize } from '@actualwave/react-native-codeditor';
|
|
102
|
+
|
|
103
|
+
export default function EditorScreen() {
|
|
104
|
+
const apiRef = useRef<WebViewAPI | null>(null);
|
|
105
|
+
|
|
106
|
+
const handleInitialized = useCallback((api: WebViewAPI) => {
|
|
107
|
+
apiRef.current = api;
|
|
108
|
+
void api.focus(); // opens Android soft keyboard
|
|
109
|
+
}, []);
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<KeyboardAvoidingView style={{ flex: 1 }} behavior={Platform.OS === 'ios' ? 'padding' : 'height'}>
|
|
113
|
+
<CodeEditor
|
|
114
|
+
content="const x = 42;"
|
|
115
|
+
language="javascript"
|
|
116
|
+
theme="darcula"
|
|
117
|
+
onInitialized={handleInitialized}
|
|
118
|
+
onContentUpdate={(content) => console.log(content.length)}
|
|
119
|
+
onHistorySizeUpdate={(size) => console.log(size.undo, size.redo)}
|
|
120
|
+
onLog={(...args) => console.log('[editor]', ...args)}
|
|
121
|
+
onError={(err) => console.error('[editor]', err)}
|
|
122
|
+
/>
|
|
123
|
+
</KeyboardAvoidingView>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Architecture
|
|
131
|
+
|
|
132
|
+
Two runtimes connected via DDA (Deferred Data Access) RPC over the WebView bridge:
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
React Native (HOST) WebView (GUEST)
|
|
136
|
+
────────────────── ───────────────
|
|
137
|
+
CodeEditor component
|
|
138
|
+
└─ WebViewAPI
|
|
139
|
+
└─ initializeHost() ◄──► initializeGuest()
|
|
140
|
+
api.editor.* ──────► EditorController methods
|
|
141
|
+
(DDA proxy) ◄────── Promise results
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Out-of-band messages** (bypass DDA, sent via `ReactNativeWebView.postMessage`):
|
|
145
|
+
|
|
146
|
+
| Type | Direction | Meaning |
|
|
147
|
+
|---|---|---|
|
|
148
|
+
| `__editorReady__` | WebView→RN | Editor fully painted; `onInitialized` fires |
|
|
149
|
+
| `__contentChange__` | WebView→RN | Edit, debounced 300ms — carries `{ value, undo, redo }` |
|
|
150
|
+
| `__selectionChange__` | WebView→RN | Selection changed — carries selected text |
|
|
151
|
+
| `__shortcut__` | WebView→RN | Registered shortcut triggered — carries name string |
|
|
152
|
+
| `__editorLog__` | WebView→RN | `window.log(...)` from WebView |
|
|
153
|
+
| `__editorError__` | WebView→RN | `window.onerror` from WebView |
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## DDA lazy mode — critical rule
|
|
158
|
+
|
|
159
|
+
DDA operates in **lazy mode**: a proxy call builds a command chain but does NOT send it until the returned value is awaited. **Every `api.editor.*` call MUST be awaited.**
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
// ✅ correct
|
|
163
|
+
await apiRef.current?.editor?.historyUndo();
|
|
164
|
+
|
|
165
|
+
// ✅ correct — async IIFE in useEffect
|
|
166
|
+
void (async () => { await apiRef.current!.editor.setLanguage('python'); })();
|
|
167
|
+
|
|
168
|
+
// ❌ wrong — command is never sent
|
|
169
|
+
void apiRef.current?.editor.historyUndo();
|
|
170
|
+
apiRef.current?.editor.historyUndo();
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Props
|
|
176
|
+
|
|
177
|
+
### Required callbacks
|
|
178
|
+
|
|
179
|
+
| Prop | Type | Description |
|
|
180
|
+
|---|---|---|
|
|
181
|
+
| `onInitialized` | `(api: WebViewAPI) => void` | Fires when editor is fully ready. Store `api` here. Call `api.focus()` for Android keyboard. |
|
|
182
|
+
| `onContentUpdate` | `(content: string) => void` | After edits (debounced 300ms), full document text. |
|
|
183
|
+
| `onHistorySizeUpdate` | `(size: HistorySize) => void` | After edits (debounced 300ms), `{ undo, redo }` depths. |
|
|
184
|
+
| `onLog` | `(...args: unknown[]) => void` | `window.log()` from the WebView. |
|
|
185
|
+
| `onError` | `(error: unknown) => void` | `window.onerror` from the WebView. |
|
|
186
|
+
|
|
187
|
+
### Optional callbacks
|
|
188
|
+
|
|
189
|
+
| Prop | Type | Description |
|
|
190
|
+
|---|---|---|
|
|
191
|
+
| `onSelectionChange` | `(text: string) => void` | Called when selection changes; receives selected text (empty string when collapsed). |
|
|
192
|
+
| `onShortcut` | `(name: string) => void` | Called when a shortcut registered via `api.editor.registerShortcut()` is triggered. Receives the name string passed at registration time. |
|
|
193
|
+
|
|
194
|
+
### Editor configuration
|
|
195
|
+
|
|
196
|
+
| Prop | Type | Default | Description |
|
|
197
|
+
|---|---|---|---|
|
|
198
|
+
| `content` | `string` | `''` | Document content. Initial value is delivered at startup; later prop changes call `setValue` if they differ from the editor's current text. |
|
|
199
|
+
| `language` | `string` | — | Syntax language, e.g. `'javascript'`, `'python'`. Loaded on demand. |
|
|
200
|
+
| `languageConfig` | `Record<string, unknown>` | — | Options passed to the language factory, e.g. `{ jsx: true, typescript: true }` for `'javascript'`. |
|
|
201
|
+
| `languages` | `Record<string, string>` | — | Custom language packages `{ name: packageName }` for names that don't follow `@codemirror/lang-{name}`, e.g. `{ sksl: '@actualwave/codemirror-lang-sksl' }`. Registered before the initial `language` is resolved. |
|
|
202
|
+
| `embeds` | `EmbedsConfig` | — | DSL highlighting + suggestions mixed into `'javascript'`: `{ taggedTemplates?: string[], supportExtensions?: (string \| [string, object])[] }`. See [Embedded DSLs](#embedded-dsls-in-javascript). Changing it re-resolves the current language — pass a stable object. |
|
|
203
|
+
| `extensions` | `ExtensionSpec[]` | `[]` | CM6 extension specs. |
|
|
204
|
+
| `theme` | `string` | — | Theme name, e.g. `'darcula'`, `'monokai'`. |
|
|
205
|
+
| `viewport` | `ViewportSettings` | — | `<meta name="viewport">` options. |
|
|
206
|
+
|
|
207
|
+
### WebView / layout
|
|
208
|
+
|
|
209
|
+
| Prop | Type | Default | Description |
|
|
210
|
+
|---|---|---|---|
|
|
211
|
+
| `editorUri` | `string` | `'file:///android_asset/codeditor/editor.html'` | Override for iOS — use `new File(Paths.bundle, 'assets/codeditor/editor.html').uri` from `expo-file-system`. |
|
|
212
|
+
| `allowFileAccess` | `boolean` | `true` | Required for `file://` asset loading. |
|
|
213
|
+
| `renderBlockingView` | `() => ReactNode` | `() => <BlockingView />` | Loading overlay while editor initialises. |
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## `WebViewAPI` — editor control
|
|
218
|
+
|
|
219
|
+
`api` is received in `onInitialized`. `api.editor` is a DDA proxy — all its methods return Promises.
|
|
220
|
+
|
|
221
|
+
### Focus
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
api.focus(): Promise<void>
|
|
225
|
+
// Also calls webView.requestFocus() for Android keyboard.
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Content
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
api.editor.getValue(): Promise<string>
|
|
232
|
+
api.editor.setValue(value: string): Promise<void> // preserves history
|
|
233
|
+
api.editor.resetValue(value?: string): Promise<void> // clears history
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Language / theme / extensions
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
api.editor.setLanguage(name: string, config?: Record<string, unknown>, embeds?: EmbedsConfig): Promise<void>
|
|
240
|
+
api.editor.registerLanguage(name: string, packageName: string): Promise<void>
|
|
241
|
+
// Maps a name to a package not following @codemirror/lang-{name}; applies on the next setLanguage().
|
|
242
|
+
// e.g. await registerLanguage('sksl', '@actualwave/codemirror-lang-sksl'); await setLanguage('sksl');
|
|
243
|
+
api.editor.registerTaggedTemplate(packageName: string): Promise<void>
|
|
244
|
+
api.editor.registerSupportExtension(packageName: string, config?: Record<string, unknown>): Promise<void>
|
|
245
|
+
// Both register globally for the page's lifetime ('javascript' only); re-call setLanguage('javascript') to apply.
|
|
246
|
+
api.editor.setTheme(themeName?: string): Promise<void> // undefined removes theme
|
|
247
|
+
api.editor.setExtensions(specs: ExtensionSpec[]): Promise<void>
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Cursor and selection
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
api.editor.getCursor(where?: 'from' | 'to' | 'head'): Promise<CursorPosition>
|
|
254
|
+
// CursorPosition = { line: number, ch: number, index: number } (line is 0-based)
|
|
255
|
+
api.editor.setCursor(line: number, ch?: number): Promise<void>
|
|
256
|
+
api.editor.getSelection(): Promise<string>
|
|
257
|
+
api.editor.setSelection(anchor: number, head?: number): Promise<void>
|
|
258
|
+
api.editor.replaceSelection(text: string): Promise<void>
|
|
259
|
+
api.editor.cancelSelection(): Promise<void>
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### History
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
api.editor.historyUndo(): Promise<boolean>
|
|
266
|
+
api.editor.historyRedo(): Promise<boolean>
|
|
267
|
+
api.editor.historyClear(): Promise<void>
|
|
268
|
+
api.editor.historySize(): Promise<HistorySize> // { undo, redo }
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Editing commands
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
api.editor.indentMore(): Promise<boolean>
|
|
275
|
+
api.editor.indentLess(): Promise<boolean>
|
|
276
|
+
api.editor.toggleComment(): Promise<boolean>
|
|
277
|
+
api.editor.moveLineUp(): Promise<boolean>
|
|
278
|
+
api.editor.moveLineDown(): Promise<boolean>
|
|
279
|
+
api.editor.deleteLine(): Promise<boolean>
|
|
280
|
+
api.editor.selectLine(): Promise<boolean>
|
|
281
|
+
api.editor.selectParentSyntax(): Promise<boolean>
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Autocomplete
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
api.editor.startCompletion(): Promise<void>
|
|
288
|
+
api.editor.setCompletions(items: CompletionItem[]): Promise<void>
|
|
289
|
+
// CompletionItem = { label, type?, detail?, info? }
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Custom shortcuts (hardware keyboard)
|
|
293
|
+
|
|
294
|
+
Register key bindings from the React Native side. When triggered in the editor, fires `onShortcut` on the `<CodeEditor>` component with the given name. Shortcuts use `Prec.highest` priority, overriding all built-in keymaps.
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
api.editor.registerShortcut(key: string, name: string): Promise<void>
|
|
298
|
+
api.editor.unregisterShortcut(key: string): Promise<void>
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Key notation: `Mod` = Cmd on macOS/iOS, Ctrl on Android/Windows. Examples: `'Mod-s'`, `'Mod-Enter'`, `'Mod-Shift-f'`, `'F5'`.
|
|
302
|
+
|
|
303
|
+
```tsx
|
|
304
|
+
// Register in onInitialized, handle in onShortcut
|
|
305
|
+
<CodeEditor
|
|
306
|
+
onInitialized={async (api) => {
|
|
307
|
+
await api.editor.registerShortcut('Mod-s', 'save');
|
|
308
|
+
await api.editor.registerShortcut('Mod-Enter', 'run');
|
|
309
|
+
}}
|
|
310
|
+
onShortcut={(name) => {
|
|
311
|
+
if (name === 'save') handleSave();
|
|
312
|
+
if (name === 'run') handleRun();
|
|
313
|
+
}}
|
|
314
|
+
...
|
|
315
|
+
/>
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Font / keyboard / scroll / advanced
|
|
319
|
+
|
|
320
|
+
```ts
|
|
321
|
+
api.editor.setFontSize(size: number): Promise<void>
|
|
322
|
+
api.editor.setSoftKeyboard(enabled: boolean): Promise<void>
|
|
323
|
+
api.editor.setViewport(options: ViewportSettings): Promise<void>
|
|
324
|
+
api.editor.scrollToCursor(margin?: number): Promise<void>
|
|
325
|
+
api.editor.loadExtension(moduleName: string): Promise<object>
|
|
326
|
+
api.editor.destroy(): Promise<void>
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Synchronous RN-side only
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
api.injectJavaScript(code: string): void
|
|
333
|
+
api.requestFocus(): void
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## Content model
|
|
339
|
+
|
|
340
|
+
`content` sets the initial document, and later prop changes are pushed to the editor via `setValue` — but only when they differ from the text the editor last reported, so writing `onContentUpdate` output back to `content` is a harmless no-op. Because `onContentUpdate` is debounced (300ms), avoid transforming the text and feeding it back while the user is typing.
|
|
341
|
+
|
|
342
|
+
To push content programmatically you can also use the API:
|
|
343
|
+
|
|
344
|
+
```ts
|
|
345
|
+
await api.editor.setValue(newCode); // preserve history
|
|
346
|
+
await api.editor.resetValue(newCode); // clear history (e.g. opening new file)
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## Extension specs
|
|
352
|
+
|
|
353
|
+
`ExtensionSpec` accepted by `extensions` prop and `api.editor.setExtensions()`:
|
|
354
|
+
|
|
355
|
+
| Form | Example | Behaviour |
|
|
356
|
+
|---|---|---|
|
|
357
|
+
| Package name string | `'@codemirror/search'` | Loaded via built-in registry |
|
|
358
|
+
| `[packageName, exportName]` | `['@uiw/codemirror-theme-nord', 'nord']` | Returns `mod[exportName]` |
|
|
359
|
+
| `[packageName, options]` | `['@codemirror/search', { top: true }]` | Resolver called with options |
|
|
360
|
+
| Pre-built extension | `myExtension` | Used as-is |
|
|
361
|
+
|
|
362
|
+
Built-in registry: `@codemirror/autocomplete`, `@codemirror/search`, `@codemirror/lint`, `@codemirror/collab`, `@codemirror/theme-one-dark`.
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## Supported languages
|
|
367
|
+
|
|
368
|
+
`angular` `cpp` `css` `go` `html` `java` `javascript` `jinja` `json` `less` `lezer`
|
|
369
|
+
`liquid` `markdown` `php` `python` `rust` `sass` `sql` `vue` `wast` `xml` `yaml`
|
|
370
|
+
|
|
371
|
+
Bundled packages that don't follow the `@codemirror/lang-{name}` naming (e.g. `sksl` →
|
|
372
|
+
`@actualwave/codemirror-lang-sksl`) are not auto-detected — map them with the `languages`
|
|
373
|
+
prop or `api.editor.registerLanguage()` first, otherwise `setLanguage` rejects with
|
|
374
|
+
"No language support found".
|
|
375
|
+
|
|
376
|
+
Legacy modes (100+ more) via `loadExtension`:
|
|
377
|
+
|
|
378
|
+
```ts
|
|
379
|
+
const { swift } = await api.editor.loadExtension('@codemirror/legacy-modes/mode/swift');
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## Themes
|
|
385
|
+
|
|
386
|
+
`androidstudio` `andromeda` `atomone` `aura` `basic` `bbedit` `copilot` `darcula`
|
|
387
|
+
`dracula` `duotone` `eclipse` `github` `material` `monokai` `nord` `okaidia` `solarized`
|
|
388
|
+
`sublime` `tokyoNight` `vscode` `xcode`
|
|
389
|
+
|
|
390
|
+
Two-word themes use camelCase: `tokyoNight`, `githubDark`, `githubLight`, `vscodeDark`, `xcodeDark`, `xcodeLight`, `solarizedDark`, `solarizedLight`.
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
## Built-in keyboard shortcuts
|
|
395
|
+
|
|
396
|
+
The editor ships with standard CodeMirror 6 keymaps active by default.
|
|
397
|
+
|
|
398
|
+
### Editing (`defaultKeymap`)
|
|
399
|
+
| Key | Action |
|
|
400
|
+
|---|---|
|
|
401
|
+
| `Enter` | Insert newline |
|
|
402
|
+
| `Backspace` / `Delete` | Delete character |
|
|
403
|
+
| `Tab` | Indent |
|
|
404
|
+
| `Shift-Tab` | Unindent |
|
|
405
|
+
| `Mod-a` | Select all |
|
|
406
|
+
|
|
407
|
+
### History (`historyKeymap`)
|
|
408
|
+
| Key | Action |
|
|
409
|
+
|---|---|
|
|
410
|
+
| `Mod-z` | Undo |
|
|
411
|
+
| `Mod-y` / `Mod-Shift-z` | Redo |
|
|
412
|
+
|
|
413
|
+
### Search (`searchKeymap`)
|
|
414
|
+
| Key | Action |
|
|
415
|
+
|---|---|
|
|
416
|
+
| `Mod-f` | Open search panel |
|
|
417
|
+
| `F3` / `Mod-g` | Find next |
|
|
418
|
+
| `Escape` | Close search panel |
|
|
419
|
+
|
|
420
|
+
### Code folding (`foldKeymap`)
|
|
421
|
+
| Key | Action |
|
|
422
|
+
|---|---|
|
|
423
|
+
| `Mod-Shift-[` | Fold at cursor |
|
|
424
|
+
| `Mod-Shift-]` | Unfold at cursor |
|
|
425
|
+
|
|
426
|
+
### Autocomplete (`completionKeymap`)
|
|
427
|
+
| Key | Action |
|
|
428
|
+
|---|---|
|
|
429
|
+
| `Ctrl-Space` | Trigger completion |
|
|
430
|
+
| `Enter` / `Tab` | Accept completion |
|
|
431
|
+
| `Escape` | Close completion |
|
|
432
|
+
|
|
433
|
+
> On mobile, most shortcuts require a hardware keyboard. Use `api.editor.*` commands programmatically instead.
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
## Keyboard / layout
|
|
438
|
+
|
|
439
|
+
Wrap in `KeyboardAvoidingView` so the editor shrinks when the soft keyboard appears:
|
|
440
|
+
|
|
441
|
+
```tsx
|
|
442
|
+
<KeyboardAvoidingView style={{ flex: 1 }} behavior={Platform.OS === 'ios' ? 'padding' : 'height'}>
|
|
443
|
+
<CodeEditor ... />
|
|
444
|
+
</KeyboardAvoidingView>
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
Call `api.focus()` inside `onInitialized` to trigger the Android soft keyboard immediately.
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
## Embedded DSLs in `javascript`
|
|
452
|
+
|
|
453
|
+
`embeds` (prop, or 3rd arg of `setLanguage`) adds highlighting **and** autocomplete suggestions for DSLs inside `'javascript'`. Only applies when the language is `'javascript'`; merged with anything registered via `registerTaggedTemplate` / `registerSupportExtension`. Must be plain serializable data (package names + configs).
|
|
454
|
+
|
|
455
|
+
```tsx
|
|
456
|
+
<CodeEditor
|
|
457
|
+
language="javascript"
|
|
458
|
+
languageConfig={{ jsx: true, typescript: true }}
|
|
459
|
+
embeds={{
|
|
460
|
+
taggedTemplates: [
|
|
461
|
+
'@actualwave/codemirror-lang-embed-css',
|
|
462
|
+
'@actualwave/codemirror-lang-embed-sql',
|
|
463
|
+
'@actualwave/codemirror-lang-embed-graphql',
|
|
464
|
+
],
|
|
465
|
+
supportExtensions: [
|
|
466
|
+
'@actualwave/codemirror-lang-embed-react-native',
|
|
467
|
+
'@actualwave/codemirror-lang-embed-tailwind',
|
|
468
|
+
],
|
|
469
|
+
}}
|
|
470
|
+
/>
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
A package must be in the list matching what it exports, else it fails to load:
|
|
474
|
+
|
|
475
|
+
| List | Packages | Provides |
|
|
476
|
+
|---|---|---|
|
|
477
|
+
| `taggedTemplates` | `embed-css`, `embed-sql`, `embed-glsl`, `embed-sksl`, `embed-icu-messageformat`, `embed-graphql` | Nested language highlighting + its suggestions (`embed-graphql`: highlighting only) |
|
|
478
|
+
| `supportExtensions` | `embed-tailwind`, `embed-react-native`, `js-completion` | Suggestions (and Tailwind class coloring) on the JS language; no nested language |
|
|
479
|
+
|
|
480
|
+
Tags match by name only: `css`/`styled`/`styled.X`/`styled(X)` → CSS, `sql`, `gql` (not `graphql`), `glsl`, `sksl`, `t` → ICU MessageFormat, `tw` → Tailwind. `embed-react-native` needs no tag (JSX, imports, `StyleSheet.create`). Only `supportExtensions` accept a config: `['pkg', config]`. No extra setup is needed for suggestions.
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
## Common pitfalls
|
|
485
|
+
|
|
486
|
+
**DDA calls not dispatched** — forgetting `await` silently does nothing. Every `api.editor.*` must be awaited.
|
|
487
|
+
|
|
488
|
+
**`onInitialized` timing** — `api` is not available until this fires. Guard all API calls with `if (!apiRef.current) return`.
|
|
489
|
+
|
|
490
|
+
**Content updates are debounced** — `onContentUpdate` fires 300ms after the last edit, so `content` may briefly lag behind the editor. Don't rewrite `content` with modified text mid-typing.
|
|
491
|
+
|
|
492
|
+
**`setLanguage` rejects for unregistered names** — custom packages like `sksl` must be registered via the `languages` prop or `registerLanguage()` first.
|
|
493
|
+
|
|
494
|
+
**Embeds silently do nothing** — a package in the wrong list, a typo, or a missing module is skipped with no error, and embeds are ignored unless `language` is `'javascript'`. Tags are exact (`gql`, not `graphql`).
|
|
495
|
+
|
|
496
|
+
**Android assets not copied** — if the WebView shows "Error loading page", the `src/assets/` files haven't been copied to `android/app/src/main/assets/codeditor/`. Re-run the copy command.
|
|
497
|
+
|
|
498
|
+
**iOS URI** — the default `editorUri` is Android-only. iOS requires `new File(Paths.bundle, 'assets/codeditor/editor.html').uri` from `expo-file-system` (SDK 54+ API; on older SDKs use `FileSystem.bundleDirectory`).
|
|
499
|
+
|
|
500
|
+
**Upgrading the library** — always re-copy assets after upgrading:
|
|
501
|
+
|
|
502
|
+
```bash
|
|
503
|
+
cp -r node_modules/@actualwave/react-native-codeditor/src/assets/* android/app/src/main/assets/codeditor/
|
|
504
|
+
```
|